package br.ufrj.dcc.ad.metrics;

import java.util.List;

import org.apache.log4j.Logger;

import br.ufrj.dcc.ad.enums.NomeMetrica;

public class MetricasGrupais {
	
	/** Confiança de 95% */
	private final static double Z_95 = 1.96;
	/**
	 * Confiança: 95%, ALFA = 0.5, ALFA/2 = 0.025
	 * Primeira posição é um valor fake já que variancia = 0 quando n = 1(g.l = 0).
	 */
	private static final double[] T_0_025 = {0.0,12.71,4.303,3.182,2.776,2.571,2.447,2.365,2.306,2.262,2.228,
											 2.201,2.179,2.160,2.145,2.131,2.120,2.110,2.101,2.093,2.086,
											 2.080,2.074,2.069,2.064,2.060,2.056,2.052,2.048,2.045,2.042};

	private static final int MAXIMOSTUDENT = 31;
	/** Nome da métrica */
	private NomeMetrica nomeMetrica;
	/** Indice da métrica. -1 caso não possua índice. Exemplo: X1 */
	private int indice;
	
	/** Conjunto de dados que será processado. */
	private List<Double> mediasIteracoes;
	private List<Double> varianciasIteracoes;
	/** Dados auxiliares para obtenção das verdadeiras métricas. */
	private double varianciaDaMedia;
	private double varianciaDaVariancia;
	
	/**
	 * Dados derivados dos conjuntos de dados. 
	 * São as verdadeiras métricas de interesse.
	 */
	private double mediaDaMedia;
	private double mediaDaVariancia;
	private IntervaloConfianca icMediaDaMedia;
	private IntervaloConfianca icMediaDaVariancia;
	
	/**
	 * Possui todas os resultados sobre a confiança de uma determinada métrica
	 * (Média, IC Média, Variância, IC Variância), esta estando presente no enum NomeMetrica.
	 * @param nomeMetrica Nome da métrica.
	 * @param indice Índice da métrica. -1 caso não possua indice.<br> Exemplo: X1 </br>
	 * @param mediasIteracoes As médias obtidas até o momento.
	 * @param varianciasIteracoes As variâncias obtidas até o momento.
	 */
	public MetricasGrupais(NomeMetrica nomeMetrica,int indice,List<Double> mediasIteracoes, List<Double> varianciasIteracoes) {
		this.nomeMetrica = nomeMetrica;
		this.mediasIteracoes = mediasIteracoes;
		this.varianciasIteracoes = varianciasIteracoes;
		this.indice = indice;
	}
	/**
	 * Adiciona a média e a variância obtidas na iteração.
	 * @param media A média obtida na iteração.
	 * @param variancia A variância obtida na iteração.
	 */
	public void addMetricasIteracao(double media, double variancia) {
		mediasIteracoes.add(media);
		varianciasIteracoes.add(variancia);
	}
	
	/**
	 * Imprime todas as métricas aqui presente. (Média, IC Média, Variância, IC Variância)
	 */
	public void printAll(Logger log){
		calculaMetricas();
		log.info(nomeMetrica.getNome() + ((indice!=-1)?indice+1:""));
		log.info("Media da Media " + mediaDaMedia + icMediaDaMedia.toString());
		log.info("Media da Variancia " + mediaDaVariancia + icMediaDaVariancia.toString());
	}
	
	private double calcularMedia(List<Double> metricas) {
		double soma = 0.0;
		for(Double metrica: metricas){
			soma += metrica.doubleValue();
		}
		return soma/metricas.size();
	}
	
	private double calcularVariancia(List<Double> metricas,double media) {
		double soma = 0.0;
		for(Double metrica:metricas){
			soma += Math.pow((metrica.doubleValue() - media), 2);
		}
		if(metricas.size() > 1){
			return soma/(metricas.size()-1);
		}
		return 0.0;
	}
	
	/**
	 * Calcula o intervalo de confiança da média com confiança dado pelo z da tabela normal.
	 * @param z_t O t da tabela t-student ou o z da tabela normal que definem o quanto de confiança temos neste intervalo.
	 * @return O intervalo de confiança da média.
	 */
	private IntervaloConfianca calcularIntervaloConfiancaMedia(double z_t,int numeroMedias) {
		double limiteInferiorMedia = mediaDaMedia - z_t*(Math.sqrt(varianciaDaMedia/numeroMedias));
		double limiteSuperiorMedia = mediaDaMedia + z_t*(Math.sqrt(varianciaDaMedia/numeroMedias));
		return new IntervaloConfianca(limiteInferiorMedia,limiteSuperiorMedia);
	}
	/**
	 * Calcula o intervalo de confiança da variância da média, 
	 * tratando a variância como uma variável aleatória.
	 * @param z O z da tabela normal que define o quanto de confiança temos neste intervalo.
	 * @return O intervalo de confiança da variância.
	 */
	private IntervaloConfianca calcularIntervaloConfiancaVariancia(double z,int numeroVariancias) {
		double limiteInferiorMedia = mediaDaVariancia - z*(Math.sqrt(varianciaDaVariancia/numeroVariancias));
		double limiteSuperiorMedia = mediaDaVariancia + z*(Math.sqrt(varianciaDaVariancia/numeroVariancias));
		return new IntervaloConfianca(limiteInferiorMedia,limiteSuperiorMedia);
	}
	
	/** Calcula todas as métricas aqui presente. */
	private void calculaMetricas() {
		this.mediaDaMedia = calcularMedia(this.mediasIteracoes);
		this.varianciaDaMedia = calcularVariancia(this.mediasIteracoes,mediaDaMedia);
		this.mediaDaVariancia = calcularMedia(this.varianciasIteracoes);
		this.varianciaDaVariancia = calcularVariancia(this.varianciasIteracoes,mediaDaVariancia);
		//Calcula intervalos de confiança com t-student.
		if(mediasIteracoes.size() <= MAXIMOSTUDENT){
			this.icMediaDaMedia = calcularIntervaloConfiancaMedia(T_0_025[this.mediasIteracoes.size()-1],this.mediasIteracoes.size());
			this.icMediaDaVariancia = calcularIntervaloConfiancaVariancia(T_0_025[this.mediasIteracoes.size()-1],this.varianciasIteracoes.size());
		//Calcula intervalos de confiança com Normal.
		}else{
			this.icMediaDaMedia = calcularIntervaloConfiancaMedia(Z_95,this.mediasIteracoes.size());
			this.icMediaDaVariancia = calcularIntervaloConfiancaVariancia(Z_95,this.varianciasIteracoes.size());
		}
		
	}
	
	public NomeMetrica getNomeMetrica() {
		return nomeMetrica;
	}
	
	public int getIndice() {
		return indice;
	}
}
